﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.Globalization;
using Elentok.Common.Helpers;

namespace Elentok.Common
{
  /// <summary>
  /// Handles file type associations
  /// </summary>
  public static class FileTypeAssociations
  {
    /// <summary>
    /// Returns the root of the classes registry key according to the scope
    /// (System scope will return HKEY_CLASSES_ROOT, and User scope will return
    /// HKEY_CURRENT_USER\Software\Classes
    /// </summary>
    /// <param name="scope"></param>
    /// <param name="writeable">Set to true if you need write access</param>
    /// <returns></returns>
    static RegistryKey GetClassesRoot(FileTypeAssociationScope scope, bool writeable)
    {
      if (scope == FileTypeAssociationScope.System)
        return Registry.ClassesRoot;
      else
        return Registry.CurrentUser.OpenSubKey(@"Software\Classes", writeable);
    }

    /// <summary>
    /// Finds the name of the filetype associated with <paramref name="extension"/>
    /// (the extension can start with "." or without it, it doesn't matter).
    /// <para>
    /// If the extension doesn't exist returns null.
    /// </para>
    /// </summary>
    /// <param name="extension"></param>
    /// <param name="scope"></param>
    /// <returns></returns>
    public static string GetAssociation(string extension, FileTypeAssociationScope scope)
    {
      if (extension[0] != '.')
        extension = "." + extension;

      RegistryKey key = GetClassesRoot(scope, false).OpenSubKey(extension, false);

      if (key == null)
        return null;

      return (string)key.GetValue(string.Empty);
    }

    /// <summary>
    /// Associates <paramref name="extension"/> with <paramref name="fileType"/>
    /// (the extension can start with "." or without it, it doesn't matter).
    /// </summary>
    /// <param name="extension"></param>
    /// <param name="fileType"></param>
    /// <param name="scope"></param>
    public static void Associate(string extension, string fileType, FileTypeAssociationScope scope)
    {
      if (extension[0] != '.')
        extension = "." + extension;

      RegistryKey classesRoot = GetClassesRoot(scope, true);

      RegistryKey key = classesRoot.OpenSubKey(extension, true);
      if (key == null)
        key = classesRoot.CreateSubKey(extension);

      key.SetValue(string.Empty, fileType);
      key.Flush();
    }

    /// <summary>
    /// Returns true if the given extension is associated with the running application
    /// </summary>
    /// <param name="extension"></param>
    /// <param name="fileTypeName"></param>
    /// <param name="scope"></param>
    /// <returns></returns>
    public static bool IsAssociatedWithThisApp(string extension, string fileTypeName, FileTypeAssociationScope scope)
    {
      string currentFileTypeName = GetAssociation(extension, scope);
      if (currentFileTypeName == null || currentFileTypeName != fileTypeName)
        return false;

      FileType fileType = GetFileType(fileTypeName, scope);
      FileTypeShellCommand shellCommand = fileType.GetCommand("open");
      string shell = "\"" + SystemHelper.ExecutablePath + "\" \"%1\"";
      return (shellCommand != null && shellCommand.Command == shell);
    }

    /// <summary>
    /// Associates the given extension with the current application
    /// </summary>
    /// <param name="extension"></param>
    /// <param name="fileTypeName"></param>
    /// <param name="description"></param>
    /// <param name="commandDescription"></param>
    /// <param name="scope"></param>
    public static void AssociateWithThisApp(string extension, string fileTypeName, string description, string commandDescription, FileTypeAssociationScope scope)
    {
      FileTypeAssociations.Associate(extension, fileTypeName, scope);
      FileType fileType = FileTypeAssociations.GetFileType(fileTypeName, scope, true);
      fileType.Description = description;
      FileTypeShellCommand shellCommand = fileType.GetCommand("open", true);
      shellCommand.Command = "\"" + SystemHelper.ExecutablePath + "\" \"%1\"";
      shellCommand.Description = commandDescription;
    }

    /// <summary>
    /// Searches the registry for the file type named "<paramref name="name"/>".
    /// <para>
    /// Returns "null" if the file type is not found.
    /// </para>
    /// </summary>
    /// <param name="name"></param>
    /// <param name="scope"></param>
    /// <returns></returns>
    public static FileType GetFileType(string name, FileTypeAssociationScope scope)
    {
      return GetFileType(name, scope, false);
    }

    /// <summary>
    /// Searches the registry for the file type named "<paramref name="name"/>".
    /// <para>
    /// If the file type is not found: if <paramref name="createIfNotFound"/> is true,
    /// it will create the file type, otherwise it will return null
    /// </para>
    /// </summary>
    /// <param name="name"></param>
    /// <param name="scope"></param>
    /// <param name="createIfNotFound"></param>
    /// <returns></returns>
    public static FileType GetFileType(string name, FileTypeAssociationScope scope, bool createIfNotFound)
    {
      RegistryKey classesRoot = GetClassesRoot(scope, true);
      RegistryKey key = classesRoot.OpenSubKey(name, true);
      if (key == null)
      {
        if (createIfNotFound)
          key = classesRoot.CreateSubKey(name);
        else
          return null;
      }
      key.Flush();
      return new FileType(key);
    }

    /// <summary>
    /// Checks if the following registry path exists:
    /// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.<paramref name="extension"/>\UserChoice
    /// <para>
    /// If so, it removes the "UserChoice" key and returns true, otherwise returns false
    /// </para>
    /// <para>
    /// The extension can argument doesn't have to start with a "."
    /// </para>
    /// </summary>
    /// <param name="extension"></param>
    /// <returns></returns>
    public static bool DeleteUserChoice(string extension)
    {
      if (extension[0] != '.')
        extension = "." + extension;

      string userChoiceKeyPath = string.Format(
        CultureInfo.InvariantCulture,
        "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.{0}\\UserChoice", 
        extension);

      if (Registry.CurrentUser.OpenSubKey(userChoiceKeyPath) != null)
      {
        Registry.CurrentUser.DeleteSubKey(userChoiceKeyPath);
        return true;
      }

      return false;
    }
  }

  /// <summary>
  /// Defines the scope in which to access file type associations
  /// ("System" uses the HKEY_CLASSES_ROOT, and "User" uses the HKEY_CURRENT_USER\Software\Classes)
  /// </summary>
  public enum FileTypeAssociationScope
  {
    /// <summary>
    /// Use the HKEY_CLASSES_ROOT
    /// </summary>
    System,
    /// <summary>
    /// Use the HKEY_CURRENT_USER\Software\Classes
    /// </summary>
    User
  }

  /// <summary>
  /// Wraps a registry file type
  /// </summary>
  public class FileType
  {
    #region Data Members
    string _description = string.Empty;
    #endregion

    #region Public Properties
    /// <summary>
    /// Get the name of this file type
    /// </summary>
    public string Name { get; private set; }
    /// <summary>
    /// Gets or sets the description of this file type
    /// </summary>
    public string Description
    {
      get { return _description; }
      set
      {
        RegistryKey.SetValue(string.Empty, value);
        RegistryKey.Flush();
        _description = value;
      }
    }
    /// <summary>
    /// Gets the registry key that represents this file type
    /// </summary>
    public RegistryKey RegistryKey { get; private set; }
    #endregion

    /// <summary>
    /// Initializes an instance of <see cref="FileType"/> from the given registry key
    /// </summary>
    /// <param name="key"></param>
    public FileType(RegistryKey key)
    {
      this.Name = key.Name;
      this.RegistryKey = key;
      this._description = (string)key.GetValue(string.Empty);
    }

    /// <summary>
    /// Searches the file type for the command named <paramref name="commandName"/>
    /// <para>
    /// Returns null if the command is not found
    /// </para>
    /// </summary>
    /// <param name="commandName"></param>
    /// <returns></returns>
    public FileTypeShellCommand GetCommand(string commandName)
    {
      return GetCommand(commandName, false);
    }

    /// <summary>
    /// Searches the file type for the command named <paramref name="commandName"/>
    /// <para>
    /// If there is no such command: if <paramref name="createIfNotFound"/> is true, 
    /// it will create the command, otherwise it will return null.
    /// </para>
    /// </summary>
    /// <param name="commandName"></param>
    /// <param name="createIfNotFound">If true, and the command doesn't exist, it will be created.</param>
    /// <returns></returns>
    public FileTypeShellCommand GetCommand(string commandName, bool createIfNotFound)
    {
      RegistryKey shell = RegistryKey.OpenSubKey("shell", true);
      if (shell == null)
      {
        if (createIfNotFound)
          shell = RegistryKey.CreateSubKey("shell");
        else
          return null;
      }

      RegistryKey commandRootKey = shell.OpenSubKey(commandName, true);
      if (commandRootKey == null)
      {
        if (createIfNotFound)
          commandRootKey = shell.CreateSubKey(commandName);
        else
          return null;
      }

      return new FileTypeShellCommand(this, commandRootKey);
    }
  }

  /// <summary>
  /// Wraps a registry file type command
  /// </summary>
  public class FileTypeShellCommand
  {
    #region Data Members
    string _description = string.Empty;
    string _command = string.Empty;
    #endregion

    #region Public Properties
    /// <summary>
    /// Gets the file type this command belongs to
    /// </summary>
    public FileType FileType { get; private set; }
    /// <summary>
    /// Gets or sets the description of this file type
    /// </summary>
    public string Description
    {
      get { return _description; }
      set
      {
        RegistryKey.SetValue(string.Empty, value);
        RegistryKey.Flush();
        _description = value;
      }
    }
    /// <summary>
    /// Gets the registry key that represents this file type
    /// </summary>
    public RegistryKey RegistryKey { get; private set; }
    /// <summary>
    /// Gets or sets the command of this command
    /// </summary>
    public string Command
    {
      get { return _command; }
      set
      {
        RegistryKey commandKey = RegistryKey.OpenSubKey("command", true);
        if (commandKey == null)
          commandKey = RegistryKey.CreateSubKey("command");
        commandKey.SetValue(string.Empty, value);
        commandKey.Flush();
        _command = value;
      }
    }
    #endregion

    /// <summary>
    /// Initializes an instance of <see cref="FileTypeShellCommand"/>
    /// </summary>
    /// <param name="fileType"></param>
    /// <param name="key"></param>
    public FileTypeShellCommand(FileType fileType, RegistryKey key)
    {
      this.FileType = fileType;
      this.RegistryKey = key;
      this._description = (string)key.GetValue(string.Empty);

      RegistryKey commandKey = key.OpenSubKey("command");
      if (commandKey != null)
        this._command = (string)commandKey.GetValue(string.Empty);
    }

  }
}
